package com.hbpu.usercenter.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.hbpu.usercenter.common.ErrorCode;
import com.hbpu.usercenter.contant.UserConstant;
import com.hbpu.usercenter.dto.UserDTO;
import com.hbpu.usercenter.exception.BusinessException;
import com.hbpu.usercenter.mapper.UserMapper;
import com.hbpu.usercenter.pojo.User;
import com.hbpu.usercenter.security.JwtUtil;
import com.hbpu.usercenter.service.UserService;
import com.hbpu.usercenter.vo.LoginUserVO;
import com.hbpu.usercenter.vo.UserLoginOutVO;
import io.jsonwebtoken.Claims;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RMapCache;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.hbpu.usercenter.contant.UserConstant.ADMIN_ROLE;


/**
 * @author 马可
 * @description 针对表【user(用户表)】的数据库操作Service实现
 * @createDate 2025-06-05 11:45:15
 */
@Schema(description = "针对表【user(用户表)】的数据库操作Service实现")
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Schema(hidden = true)
    final String SALT = "Kefan";
    @Schema(hidden = true)
    private final UserMapper userMapper;
    @Schema(hidden = true)
    private final RedisTemplate<String, Object> redisTemplate;
    private final RedissonClient redissonClient;

    public UserServiceImpl(UserMapper userMapper, RedisTemplate<String, Object> redisTemplate, RedissonClient redissonClient) {
        this.userMapper = userMapper;
        this.redisTemplate = redisTemplate;
        this.redissonClient = redissonClient;
    }


    /**
     * 注册用户
     *
     * @param userAccount   用户账号
     * @param userPassword  用户密码
     * @param checkPassword 确认密码
     * @return 用户ID
     */
    @Override
    public long registerUser(String userAccount, String userPassword, String checkPassword) {

        if (StringUtils.isAnyBlank(userAccount, userPassword, checkPassword)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
        }
        if (userAccount.length() < 4) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户账号过短");
        }
        if (userPassword.length() < 8 || checkPassword.length() < 8) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户密码过短");
        }

        String regax = "^[a-zA-Z0-9]{4,16}$";
        boolean matches = userAccount.matches(regax);
        if (!matches) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账户名格式不正确");
        }
        if (!checkPassword.equals(userPassword)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码不一致");
        }

        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        userQueryWrapper.eq("userAccount", userAccount);
        long count = this.count(userQueryWrapper);
        if (count > 0) {
            log.info("用户重复！");
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号重复");
        }

        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
        User user = new User();
        user.setUserAccount(userAccount);
        user.setUserPassword(encryptPassword);

        boolean save = this.save(user);

        if (!save) {
            log.info("用户插入失败！");
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "注册失败");
        }

        log.info("用户注册成功！");
        return user.getId();
    }

    /**
     * 用户登录
     *
     * @param userAccount  用户账号
     * @param userPassword 用户密码
     * @param request      用户请求
     * @return 登录成功返回User对象，否则返回null
     */
    @Override
    public LoginUserVO loginUser(String userAccount, String userPassword, HttpServletRequest request) {

        if (StringUtils.isAnyBlank(userAccount, userPassword)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数错误");
        }
        if (userAccount.length() < 4) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账户过短");
        }
        if (userPassword.length() < 8) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码过短");
        }

        String regax = "^[a-zA-Z0-9]{4,16}$";
        boolean matches = userAccount.matches(regax);
        if (!matches) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账户格式不正确");
        }

        String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
        LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<>();
        userLambdaQueryWrapper.eq(User::getUserAccount, userAccount)
                .eq(User::getUserPassword, encryptPassword);
        User selectUser = this.getOne(userLambdaQueryWrapper);
        if (selectUser == null) {
            log.info("用户登录失败：账号不存在或密码错误！");
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户登录失败：账号不存在或密码错误！");
        }

        // 创建一个返回给前端的响应体
        LoginUserVO loginUserVO = new LoginUserVO();
        // 将登陆用户的用户id传入以获取token
        String token = JwtUtil.generateJwt(selectUser.getId().toString());
        // 将当前登录用户信息映射给响应体
        BeanUtils.copyProperties(selectUser, loginUserVO);
        // 将生成好的token放入响应体
        loginUserVO.setToken(token);
        // 使用用户id拼接来组成独立的每个key
        String redisKey = String.format("friend-match:user:login:token:%s", loginUserVO.getId());
        // 创建一个Map
        RMapCache<Object, Object> mapCache = redissonClient.getMapCache(redisKey);
        // 设置其值
        mapCache.put("token", token, 30, TimeUnit.MINUTES);
        mapCache.put("object", loginUserVO, 30, TimeUnit.MINUTES);

        return loginUserVO;

    }

    //用户信息脱敏，不想传输的数据直接不赋值
    @Override
    public <T> UserDTO getProcessedUser(T selectUser) {
        UserDTO userDTO = new UserDTO();
        BeanUtils.copyProperties(selectUser, userDTO);
        return userDTO;
    }

    /**
     * 用户注销
     *
     * @param request 用户请求
     * @return 注销成功返回UserLoginOutResponse对象
     */
    @Override
    public UserLoginOutVO loginOut(HttpServletRequest request) {
        // 获取请求头中的Authorization，因为它一般存储着token
        String authHeader = request.getHeader("Authorization");
        // 判断是否为空，且其中内容是否正确
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            // 去掉 "Bearer " 前缀
            String token = authHeader.substring(7);
            // 解析token
            Claims claims = JwtUtil.parseToken(token);
            // 使用token中负载的主题来拼接redis的key
            String redisKey = String.format("friend-match:user:login:token:%s", claims.getSubject());
            // 通过key来获取相应的map并进行删除
            boolean delete = redissonClient.getMapCache(redisKey).delete();
            if (!delete) {
                throw new BusinessException(ErrorCode.SYSTEM_ERROR, "注销失败！");
            }
            UserLoginOutVO userLoginOutVO = new UserLoginOutVO();
            userLoginOutVO.setResult(UserConstant.LOGINOUT_SUCCESS);
            userLoginOutVO.setMessage("注销成功！");
            return userLoginOutVO;
        } else {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "获取token时出现错误！");
        }

    }

    /**
     * 根据用户名查找用户，模糊查询
     *
     * @param username 用户名
     * @return 查询到返回User类型的List集合，否则返回null
     */
    @Override
    public List<UserDTO> selectUsersByUsername(String username) {
        LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<>();
        userLambdaQueryWrapper.like(User::getUsername, username);
        //待改进点，使用steam流对查询到的多个对象进行脱敏(已改进)
        return this.list(userLambdaQueryWrapper).stream().map(this::getProcessedUser).toList();
    }

    /**
     * 查询所有用户
     *
     * @return 返回用户的List集合
     */
    @Override
    public List<UserDTO> selectAllUsers() {
        return this.list().stream().map(this::getProcessedUser).toList();
    }

    /**
     * 根据用户id删除用户
     *
     * @param id 用户id
     * @return 删除成功返回true，否则返回false
     */
    @Override
    public Boolean deleteUserById(long id) {
        return this.removeById(id);
    }

    /**
     * 查询当前用户是否登录
     *
     * @param request 用户请求
     * @return 返回脱敏的登录对象
     */
    @Override
    public UserDTO getCurrentUser(HttpServletRequest request) {
        // 获取请求头中的Authorization，因为它一般存储着token
        String authHeader = request.getHeader("Authorization");
        // 判断是否为空，且其中内容是否正确
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            // 去掉 "Bearer " 前缀
            String token = authHeader.substring(7);
            // 解析token
            Claims claims = JwtUtil.parseToken(token);
            // 使用token中负载的主题来拼接redis的key
            String redisKey = String.format("friend-match:user:login:token:%s", claims.getSubject());
            // 通过key来获取相应的map和其中存储的对象
            LoginUserVO loginUserVO = (LoginUserVO) redissonClient.getMapCache(redisKey).get("object");
            // 判断所获取的对象是否存在
            if (loginUserVO == null) {
                throw new BusinessException(ErrorCode.NULL_ERROR, "无当前登录用户信息！");
            }
            // 存在则返回处理后的对象
            return getProcessedUser(loginUserVO);
        } else {
            throw new BusinessException(ErrorCode.NULL_ERROR, "获取token时出现异常");
        }
//        // TODO 检验用户是否合法
//        User user = (User) attribute;
        // 这里先使用登录对象获取id，然后根据id去数据库中查询登录对象，确保用户的数据是最新的
        //如果不使用查询，而直接然会Session中查询出来的对象，会导致用户数据不是最新的，
        // 还是上次登录时候的数据
//        return getProcessedUser(this.getById(user.getId()));
    }

    /**
     * 判断是否为管理员权限
     *
     * @param request 用户请求
     * @return 是管理员返回true，否则返回false
     */
    @Override
    public boolean isAdmin(HttpServletRequest request) {
        UserDTO currentUser = getCurrentUser(request);
        return currentUser != null && currentUser.getUserRole() == ADMIN_ROLE;
    }

    /**
     * 判断用户是否登录
     *
     * @param request 用户请求
     * @return 登录返回true，否则返回false
     */
    @Override
    public boolean isLogin(HttpServletRequest request) {
        return true;
    }

    /**
     * 使用数据库根据tag标签搜索用户
     *
     * @param tagNames 标签名称
     * @return 返回所查询的User对象
     */
    @Override
    public List<UserDTO> selectByTags(List<String> tagNames) {
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        for (String tagName : tagNames) {
            userQueryWrapper.like("tags", tagName);
        }
        List<User> list = this.list(userQueryWrapper);
        if (list.isEmpty()) {
            throw new BusinessException(ErrorCode.NULL_ERROR);
        }
        return list.stream().map(this::getProcessedUser).toList();
    }

    /**
     * 使用内存处理数据，并通过标签搜索永固
     *
     * @param tagNames 标签名
     * @return 返回用户所查询的用户列表
     */
    @Override
    public List<UserDTO> searchByTags(List<String> tagNames) {
        if (tagNames == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        List<User> userList = this.list();
        Gson gson = new Gson();
        return userList.stream().filter(user -> {
            String tags = user.getTags();
            Set<String> tempTagNameSet = gson.fromJson(tags, new TypeToken<Set<String>>() {
            }.getType());
            tempTagNameSet = Optional.ofNullable(tempTagNameSet).orElse(new HashSet<>());
            for (String tagName : tagNames) {
                if (tempTagNameSet.contains(tagName)) {
                    return true;
                }
            }
            return false;
        }).map(this::getProcessedUser).collect(Collectors.toList());
    }

    @Override
    public int updateUser(User user, HttpServletRequest request) {
        //判断当前传入的参数是否为空
        if (user == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        Long updateUserId = user.getId();
//        判断当前传入的要修改的用户的id是否正确
        if (updateUserId <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        UserDTO currentUser = getCurrentUser(request);
//        如果是管理员可以直接进行修改，如果不是管理员则判断要修改的用户和
//        当前登录用的id是否一致，是的话才可以修改，否则没有权限
        if (!isAdmin(request) && !Objects.equals(currentUser.getId(), updateUserId)) {
            throw new BusinessException(ErrorCode.NO_AUTH);
        }
        int updateResult = userMapper.updateById(user);
        // 判断修改的用户是否是自己，是自己的话才需要刷新rediss缓存数据
        if (Objects.equals(currentUser.getId(), updateUserId)) {
            // 获取请求头中的Authorization，因为它一般存储着token
            String authHeader = request.getHeader("Authorization");
            // 判断是否为空，且其中内容是否正确
            if (authHeader != null && authHeader.startsWith("Bearer ")) {
                // 去掉 "Bearer " 前缀
                String token = authHeader.substring(7);
                // 解析token
                Claims claims = JwtUtil.parseToken(token);
                // 使用token中负载的主题来拼接redis的key
                String redisKey = String.format("friend-match:user:login:token:%s", claims.getSubject());
                // 通过key来获取相应的map并进行存储对象的更新
                RMapCache<Object, Object> mapCache = redissonClient.getMapCache(redisKey);
                Object object = mapCache.put("object", this.getProcessedUser(this.getById(updateUserId)));
                // 校验执行结果
                if (updateResult == 1 && object != null) {
                    return updateResult;
                } else {
                    throw new BusinessException(ErrorCode.SYSTEM_ERROR, "更新失败！");
                }
            } else {
                throw new BusinessException(ErrorCode.SYSTEM_ERROR, "更新失败");
            }
        } else {
            return updateResult;
        }
    }

    /**
     * 根据用户标签给用户推荐相似度高的用户
     *
     * @param request 用户请求
     * @return 返回一个用户列表
     */
    @Override
    public List<UserDTO> recommendUsers(long current, long size, HttpServletRequest request) {
        //todo 根据用户搜索标签相似的用户
        // 先获取分页数据并判断是否有下一页，如果有则执行后面的程序，反之则直接抛出异常
        Page<User> userPage = new Page<>(current, size);
        Page<User> page = this.page(userPage);
        if (!page.hasNext()) {
            throw  new BusinessException(ErrorCode.PARAMS_ERROR, "已没有最新数据");
        }
        //获取当前登录的用户，以便获取用户id
        UserDTO currentUser = this.getCurrentUser(request);
        String redisKey = String.format("friend-match:user:recommend:%s:page:%d", currentUser.getId(), current);
        //获取操作Redis中<k,v>存储结构的对象
        ValueOperations<String, Object> stringObjectValueOperations = redisTemplate.opsForValue();
        //先从Redis中利用key查询是否缓存了数据，如果有就从Redis中取数据并返回
        List<UserDTO> usersFromRedis = null;
        try {
            usersFromRedis = (List<UserDTO>) stringObjectValueOperations.get(redisKey);
        } catch (Exception e) {
            log.error("Redis cast List error", e);
            return null;
        }
        if (usersFromRedis != null) {
            return usersFromRedis;
        }
        //如果没有就从数据库中查，然后放入Redis中并返回给前端，下一次查询就可以从Redis中查询了
        List<UserDTO> usersFromMySQL = page.getRecords().stream().map(this::getProcessedUser).toList();
        //使用try-catch包围，这样即使放入Redis中发生错误也不影响返回给前端数据
        try {
            //给Redis注入数据并设置过期时间
            stringObjectValueOperations.set(redisKey, usersFromMySQL, 30, TimeUnit.MINUTES);
        } catch (Exception e) {
            log.error("redis set key error", e);
        }
        return usersFromMySQL;
    }

}